Analyse des AfD-Wahlerfolgs bei der Bundestagswahl 2021

1 Hintergrund

1.1 Relevanz des Themas

Die Bundestagswahl ist ein zentrales Ereignis für die Öffentlichkeit in Deutschland. In Anbetracht der hohen Relevanz dieses Ereignisses stellt sich die Frage, welche Faktoren die Ursache (oder zumindest Prädiktoren) der Wahlentscheidungen der Bürgerinnen und Bürger sind. Ist beispielsweise die Höhe der Arbeitslosigkeit in einem Wahlkreis ausschlaggebend, dass die eine oder andere Partei gewählt wird? Spielt die Altersverteilung eine Rolle? Der Ausländeranteil?

Die Analyse des Wahlerfolgs der Partei “Alternative für Deutschland” (AfD) ist in diesem Zusammenhang von besonderem Interesse, da ein Teil ihrer Wählis und Vertretis offenbar die Grundsätze deutscher Politik hinterfragt und vielleicht entgegensteht. So hat das Bundesamt für Verfassungsschutz Teilorganisationen der AfD zum Verdachtsfall für Rechtsextremismus eingestuft.

Die Relevanz der Analyse extremistischer Bewegungen begründet sich mit einem Blick in die deutsche Geschichte: Die (deutsche) Geschichte des 20. Jahrhunderts zeigt, dass rechtsextreme Bewegungen das Potenzial für katastrophale Entwicklungen und schlimmste Verbrechen haben.

Ziel und Gegenstand dieses Workshops ist es nicht, eine Meinung oder ein Urteil über die Einschätzung des Verfassungsschutzes oder der AfD zu treffen. Vielmehr ist vor dem genannten Hintergrund die Analyse, warum bzw. unter welchen Randbedingungen die AfD Stimmen auf sich zieht (bei der Bundestagswahl, BTW, 2021), von (potenziell) hohem gesellschaftlichem Interesse.

1.2 Hinweise

Ziel dieser Analyse ist es, grundlegende Methoden der Datenanalyse für eine angewandte Forschungsfrage bzw. eine Forschungsfrage von allgemeinem Interesse, vorzustellen bzw. einzuüben.

Es handelt sich um eine Analyse mit rein didaktischem Ziel.

Es sind keinerlei politische Aussagen mit dieser Analyse verbunden.

1.3 Forschungsfragen

  1. Bei der BTW 2021, wie groß ist der AfD-Stimmenanteil, aggregiert pro Bundesland und deutschlandweit? Wie verteilt sich der Stimmenanteil der AfD?

  2. Wie hängt der Wahlerfolg der AfD mit sozioökonomischen Indikatoren wie Arbeitslosigkeitsquote und Ausländeranteil zusammen?

  3. Welche Rolle spielen die Besonderheiten der Bundesländer, über die sozioökonomischen Indikatoren im Hinblick auf den Zweitstimmenanteil der AfD?

Die Forschungsfragen sind deskriptiv in dem Sinne, dass sie keine Kausalfragen adressieren, sondern lediglich (statistische) Zusammenhänge.

2 IT-Setup

2.1 Dateien herunterladen

Am einfachsten: Laden Sie den Github-Projektordner für diesen Workshop herunter. Dann haben sie gleich alle nötigen Dateien in der gleichen Struktur, wie in dieser Rmd-Datei aufgeführt.

Wenn Sie auf den grünen Button (“Code”) klicken, können Sie den ganzen Projektordner herunterladen.

2.2 R-Pakete laden

Nicht vergessen, dass die R-Pakete installiert sein müssen.

library(tidyverse)  # Datenjudo
library(sf)  # Geo-Visualisierung
library(rstatix)  # Deskriptive Statistiken
library(corrr)  # Korrelationsmatrizen
library(gt)  # HTML Tabellen
library(rstanarm)  # Bayes-Modellierung
library(tictoc)  # Messung der Rechenzeit
library(bayesplot)  # Visualisierung von Bayes-Modellen

2.3 Sonstige Vorbereitung

Anderes ggplot-Theme setzen:

theme_set(theme_minimal())

3 Daten aufbereiten

3.1 Ökonomopolitische Strukturdaten

3.1.1 Daten einlesen

Die Strukturdaten sind vom Bundeswahlleiter zu beziehen; über diesen Link kommt man zu den Daten (CSV-Format).

Die Variablennamen sind hier erklärt.

d_str_file <- "https://bundeswahlleiter.de/dam/jcr/b1d3fc4f-17eb-455f-a01c-a0bf32135c5d/btw21_strukturdaten.csv"

d_str <- read_delim(d_str_file,
                    delim = ";", 
                    escape_double = FALSE,
                    locale = locale(decimal_mark = ",",
                                    grouping_mark = "."),
                    trim_ws = TRUE,
                    skip = 8) 

Hier sind die Namen der Spalten:

names(d_str)
##  [1] "Land"                                                                                                      
##  [2] "Wahlkreis-Nr."                                                                                             
##  [3] "Wahlkreis-Name"                                                                                            
##  [4] "Gemeinden am 31.12.2019 (Anzahl)"                                                                          
##  [5] "Fläche am 31.12.2019 (km²)"                                                                                
##  [6] "Bevölkerung am 31.12.2019 - Insgesamt (in 1000)"                                                           
##  [7] "Bevölkerung am 31.12.2019 - Deutsche (in 1000)"                                                            
##  [8] "Bevölkerung am 31.12.2019 - Ausländer/-innen (%)"                                                          
##  [9] "Bevölkerungsdichte am 31.12.2019 (EW je km²)"                                                              
## [10] "Zu- (+) bzw. Abnahme (-) der Bevölkerung 2019 - Geburtensaldo (je 1000 EW)"                                
## [11] "Zu- (+) bzw. Abnahme (-) der Bevölkerung 2019 - Wanderungssaldo (je 1000 EW)"                              
## [12] "Alter von ... bis ... Jahren am 31.12.2019 - unter 18 (%)"                                                 
## [13] "Alter von ... bis ... Jahren am 31.12.2019 - 18-24 (%)"                                                    
## [14] "Alter von ... bis ... Jahren am 31.12.2019 - 25-34 (%)"                                                    
## [15] "Alter von ... bis ... Jahren am 31.12.2019 - 35-59 (%)"                                                    
## [16] "Alter von ... bis ... Jahren am 31.12.2019 - 60-74 (%)"                                                    
## [17] "Alter von ... bis ... Jahren am 31.12.2019 - 75 und mehr (%)"                                              
## [18] "Bodenfläche nach Art der tatsächlichen Nutzung am 31.12.2019 - Siedlung und Verkehr (%)"                   
## [19] "Bodenfläche nach Art der tatsächlichen Nutzung am 31.12.2019 - Vegetation und Gewässer (%)"                
## [20] "Fertiggestellte Wohnungen 2019 (je 1000 EW)"                                                               
## [21] "Bestand an Wohnungen am 31.12.2019 - insgesamt (je 1000 EW)"                                               
## [22] "Wohnfläche am 31.12.2019 (je Wohnung)"                                                                     
## [23] "Wohnfläche am 31.12.2019 (je EW)"                                                                          
## [24] "PKW-Bestand am 01.01.2020 - PKW insgesamt (je 1000 EW)"                                                    
## [25] "PKW-Bestand am 01.01.2020 - PKW mit Elektro- oder Hybrid-Antrieb (%)"                                      
## [26] "Unternehmensregister 2018 - Unternehmen insgesamt (je 1000 EW)"                                            
## [27] "Unternehmensregister 2018 - Handwerksunternehmen (je 1000 EW)"                                             
## [28] "Schulabgänger/-innen beruflicher Schulen 2019"                                                             
## [29] "Schulabgänger/-innen allgemeinbildender Schulen 2019 - insgesamt ohne Externe (je 1000 EW)"                
## [30] "Schulabgänger/-innen allgemeinbildender Schulen 2019 - ohne Hauptschulabschluss (%)"                       
## [31] "Schulabgänger/-innen allgemeinbildender Schulen 2019 - mit Hauptschulabschluss (%)"                        
## [32] "Schulabgänger/-innen allgemeinbildender Schulen 2019 - mit mittlerem Schulabschluss (%)"                   
## [33] "Schulabgänger/-innen allgemeinblldender Schulen 2019 - mit allgemeiner und Fachhochschulreife (%)"         
## [34] "Kindertagesbetreuung am 01.03.2020 - Betreute Kinder unter 3 Jahre (Betreuungsquote)"                      
## [35] "Kindertagesbetreuung am 01.03.2020 - Betreute Kinder 3 bis unter 6 Jahre (Betreuungsquote)"                
## [36] "Verfügbares Einkommen der privaten Haushalte 2018 (EUR je EW)"                                             
## [37] "Bruttoinlandsprodukt 2018 (EUR je EW)"                                                                     
## [38] "Sozialversicherungspflichtig Beschäftigte am 30.06.2020 - insgesamt (je 1000 EW)"                          
## [39] "Sozialversicherungspflichtig Beschäftigte am 30.06.2020 - Land- und Forstwirtschaft, Fischerei (%)"        
## [40] "Sozialversicherungspflichtig Beschäftigte am 30.06.2020 - Produzierendes Gewerbe (%)"                      
## [41] "Sozialversicherungspflichtig Beschäftigte am 30.06.2020 - Handel, Gastgewerbe, Verkehr (%)"                
## [42] "Sozialversicherungspflichtig Beschäftigte am 30.06.2020 - Öffentliche und private Dienstleister (%)"       
## [43] "Sozialversicherungspflichtig Beschäftigte am 30.06.2020 - Übrige Dienstleister und \"\"ohne Angabe\"\" (%)"
## [44] "Empfänger/-innen von Leistungen nach SGB II  Oktober 2020 -  insgesamt (je 1000 EW)"                       
## [45] "Empfänger/-innen von Leistungen nach SGB II  Oktober 2020 -  nicht erwerbsfähige Hilfebedürftige (%)"      
## [46] "Empfänger/-innen von Leistungen nach SGB II  Oktober 2020 -  Ausländer/-innen (%)"                         
## [47] "Arbeitslosenquote Februar 2021 - insgesamt"                                                                
## [48] "Arbeitslosenquote Februar 2021 - Männer"                                                                   
## [49] "Arbeitslosenquote Februar 2021 - Frauen"                                                                   
## [50] "Arbeitslosenquote Februar 2021 - 15 bis 24 Jahre"                                                          
## [51] "Arbeitslosenquote Februar 2021 - 55 bis 64 Jahre"                                                          
## [52] "Fußnoten"

Es ist vielleicht praktisch, die Spaltennamen für spätere Verwendung in einer Textdatei abzuspeichern:

d_str_names <-
  tibble(
    var_name = names(d_str),
    ) %>% 
  mutate(id = row_number())

write_csv(d_str_names, "objects/d_str_names.csv")

3.1.2 Daten aufbereiten

Die Spaltennamen sind etwas unhandlich. Formulieren wir lieber prägnanter:

names(d_str) <- paste0("V",1:ncol(d_str))

d_str2 <-
  d_str %>% 
  select(state = V1,
         area_nr = V2,
         area_name = V3,
         for_prop = V8,
         pop_density = V9,
         pop_move = V11,
         income = V36,
         unemp = V47) 

Sichern wir diese Daten in eine Datei:

write_csv(d_str2, file = "objects/d_str2.csv")

Oder als Excel-Exitstrategie:

writexl::write_xlsx(d_str2, path = "objects/d_str2.xlsx")

3.2 Wahlergebnisse

3.2.1 Daten einlesen

Die Daten sind vom Bundeswahlleiter zu beziehen. Unter diesem Link kommt man direkt zur CSV-Datei.

Eine Erklärung zu den Variablen findet sich hier.

elec_results_file <- "https://www.bundeswahlleiter.de/bundestagswahlen/2021/ergebnisse/opendata/csv/kerg2.csv"

elec_results <- read_delim(elec_results_file,
                    delim = ";", 
                    escape_double = FALSE,
                    locale = locale(decimal_mark = ",",
                                    grouping_mark = "."),
                    trim_ws = TRUE,
                    skip = 9
                    ) 

Alternativ kann man die Datei selber herunterladen und im Verzeichnis der Rmd-Datei abspeichern. Man kann als CSV-Datei oder als XLSX-Datei abspeichern und dann in R importieren. Etwas nervig ist, dass das deutsche Excel (im Standard) keine Standard-CSV erstellt.

Hier nur bruchstückhaft dargestellt; so können Sie die Datendatei importieren:

elec_results <- read_csv("kerg2_aufbereitet.csv")
elec_results <- read_csv2("kerg2_aufbereitet.csv")  # Deutsches Excel!
elec_results <- readxl::read_xlsx("kerg2_aufbereitet.xslx")

kerg2_aufbereitet.csv meint, Sie haben die ersten paar Zeilen selber gelöscht und insgesamt dafür gesorgt, dass es eine schöne, maschinenfreundliche Datendatei ist.

Eine CSV-Datei, die mit dem deutschen Excel erstellt wurde, können Sie mit read_csv2(dateiname) einlesen.

Achtung: Excel schreibt manchmal in Ihre CSV-Datei auch ohne dass Sie auf Speichern klicken! Fragen Sie mich nicht, warum Excel sich diese Freiheit nimmt.

3.2.2 Daten aufbereiten

Konzentrieren wir uns auf die Zweitstimme, da die bei der BTW die entscheidende ist.

elec_results2 <- 
  elec_results %>% 
  select(Gebietsart, Gebietsnummer, Gebietsname, UegGebietsart, UegGebietsnummer, Gruppenart, Gruppenname, Stimme, Prozent, DiffProzentPkt) %>% 
  filter(Gruppenname == "AfD") %>% 
  filter(Stimme == 2)

Im anderen Datensatz wird “Bundesgebiet” mit “Deutschland” bezeichnet. Um die beiden Datensätze im Weiteren zusammenfügen zu können, sollten jeweils die gleiche Bezeichnung (für das gleiche “Ding”, sprich Gesamtdeutschland) verwenden werden:

elec_results2 <- 
  elec_results2 %>% 
  mutate(Gebietsname = ifelse(Gebietsname == "Bundesgebiet",
                              "Deutschland",
                              Gebietsname))

Sichern wir diese Daten in eine Datei:

write_csv(elec_results2, file = "objects/elec_results2.csv")

Der Befehl könnte voraussetzen, dass der Ordner objects existiert. Alternativ können Sie auch einfach ins aktuelle Verzeichnis schreiben, das ist einfacher (aber nicht so aufgeräumt):

write_csv(elec_results2, file = "elec_results2.csv")

4 Einfache, univariate Ergebnisse

4.1 Mittelwert des AfD-Wahlerfolgs

4.1.1 … über alle Bundesländer

Achtung! Alle Bundesländer werden in der folgenden Analyse gleich gewichtet!

elec_results2 %>% 
  filter(Gebietsart == "Land") %>% 
  summarise(AfD_mean = mean(Prozent))
## # A tibble: 1 × 1
##   AfD_mean
##      <dbl>
## 1     12.1

4.1.2 … über Deutschland

elec_results2 %>% 
  filter(Gebietsart == "Bund") %>% 
  summarise(AfD_mean = mean(Prozent)) 
## # A tibble: 1 × 1
##   AfD_mean
##      <dbl>
## 1     10.3

4.1.3 … über Wahlkreise

elec_results2 %>% 
  filter(Gebietsart == "Wahlkreis") %>% 
  summarise(AfD_mean = mean(Prozent, na.rm = T))
## # A tibble: 1 × 1
##   AfD_mean
##      <dbl>
## 1     10.5

4.2 Wahlerfolg der AfD nach Bundesländern

elec_results2 %>%
  filter(Gebietsart == "Land") %>% 
  select(Gebietsname, Prozent) %>% 
  mutate(Gebietsname = as.factor(Gebietsname)) %>% 
  ggplot(aes(x =  reorder(Gebietsname, Prozent), 
             y = Prozent)) +
  geom_col() +
  coord_flip() +
  labs(title = "AfD-Zweitstimmenanteil bei der BTW 21",
       caption = "Die Linie zeigt den Mittelwert für ganz Deutschland",
       y = "Anteil in Prozent",
       x = "Bundesländer") +
  geom_hline(yintercept = 10.1) +
  annotate("label", x = "Hamburg", y = 10,
           label = "Mittelwert",
           size = 2) +
  geom_text(aes(label = round(Prozent)),
            nudge_y = -1,
            color = "white",
            size = 2)

5 Daten zusammenführen (join)

Mit einem “Join” lassen sich Tabellen zeilenweise zusammenführen. Hier findet sich eine nette visuelle Einführung.

Es finden sich viele Tutorials online zu Joins. Dieses ist ein empfehlenswertes.

5.1 Erster Versuch

d <- 
  d_str2 %>% 
  full_join(elec_results2, by = c("area_name" = "Gebietsname"))

Einige Zeilen lassen sich nicht zusammenführen. Schauen wir diese uns näher an:

d %>% 
  filter(str_detect(area_name, "nsgesamt"))  # Ohne "I"!
## # A tibble: 17 × 17
##    state area_nr area_name for_prop pop_density pop_move income unemp Gebietsart
##    <chr> <chr>   <chr>        <dbl>       <dbl>    <dbl>  <dbl> <dbl> <chr>     
##  1 Schl… 901     Land ins…      8.4        184.      6    22833   6.3 <NA>      
##  2 Meck… 913     Land ins…      4.7         69       5    19470   8.7 <NA>      
##  3 Hamb… 902     Land ins…     16.5       2446.      2.7  25029   8.1 <NA>      
##  4 Nied… 903     Land ins…      9.7        168.      4.4  21988   6.1 <NA>      
##  5 Brem… 904     Land ins…     18.5       1624.     -1.1  21481  11.6 <NA>      
##  6 Bran… 912     Land ins…      5           85       9.1  20475   6.6 <NA>      
##  7 Sach… 915     Land ins…      5.1        107.      1.1  19528   8.3 <NA>      
##  8 Berl… 911     Land ins…     19.2       4118.      6.3  20972  10.6 <NA>      
##  9 Nord… 905     Land ins…     13.6        526.      2.6  22294   7.9 <NA>      
## 10 Sach… 914     Land ins…      5.1        221.      3.8  20335   6.6 <NA>      
## 11 Hess… 906     Land ins…     16.6        298.      4.5  23943   5.7 <NA>      
## 12 Thür… 916     Land ins…      5.2        132.      1.6  19793   6.4 <NA>      
## 13 Rhei… 907     Land ins…     11.5        206.      5    23197   5.6 <NA>      
## 14 Baye… 909     Land ins…     13.6        186.      4.5  25309   4.2 <NA>      
## 15 Bade… 908     Land ins…     15.9        310.      3.4  24892   4.4 <NA>      
## 16 Saar… 910     Land ins…     11.4        384.      2.2  20277   7.4 <NA>      
## 17 Deut… 999     Insgesamt     12.5        233.      3.9  22899   6.3 <NA>      
## # … with 8 more variables: Gebietsnummer <chr>, UegGebietsart <chr>,
## #   UegGebietsnummer <chr>, Gruppenart <chr>, Gruppenname <chr>, Stimme <dbl>,
## #   Prozent <dbl>, DiffProzentPkt <dbl>

Es sind die Bundesländer, deren area_name “Land insgesamt”, jeweils, lautet.

Diese Felder müssen wir wohl umbenennen.

Bundesländer - plus der Bund als Ganzes - haben eine ID, die mit 9 beginnt, was für sonstige Einheiten nicht der Fall ist:

d_leander <- 
d %>% 
  select(area_nr, area_name, state) %>% 
  filter(str_detect(area_nr, "^9")) %>% 
  mutate(area_name = state) %>% 
  select(-state)

Da die Länder auch noch noch eine andere Gebietsnummer haben in der Datei mit den Strukturdaten, fügen wir noch die Gebietsnummer aus elec_results2 hinzu:

d_laender2 <-
  d_leander %>% 
  left_join(elec_results2 %>% select(Gebietsname, Gebietsnummer), by = c("area_name" = "Gebietsname"))

Sieht dann so aus:

d_laender2
## # A tibble: 17 × 3
##    area_nr area_name              Gebietsnummer
##    <chr>   <chr>                  <chr>        
##  1 901     Schleswig-Holstein     01           
##  2 913     Mecklenburg-Vorpommern 13           
##  3 902     Hamburg                02           
##  4 903     Niedersachsen          03           
##  5 904     Bremen                 04           
##  6 912     Brandenburg            12           
##  7 915     Sachsen-Anhalt         15           
##  8 911     Berlin                 11           
##  9 905     Nordrhein-Westfalen    05           
## 10 914     Sachsen                14           
## 11 906     Hessen                 06           
## 12 916     Thüringen              16           
## 13 907     Rheinland-Pfalz        07           
## 14 909     Bayern                 09           
## 15 908     Baden-Württemberg      08           
## 16 910     Saarland               10           
## 17 999     Deutschland            99

5.2 Zweiter Versuch

Jetzt fügen wir die korrigierten Landesnamen und -nummern zu d_str2 hinzu:

d_str3 <-
  d_str2 %>% 
  left_join(d_laender2, by = "area_nr")

area_name.y ist aktuell nur mit Landesnamen (plus Bund) gefüllt. Ergänzen wir also die NAs mit den Namen der Wahlbezirke. Die andere Spalte area_name.x brauchen wir dann nicht mehr.

d_str4 <- 
  d_str3 %>% 
  mutate(area_name.y = ifelse(is.na(area_name.y),
                              area_name.x,
                              area_name.y)) %>% 
  select(-area_name.x) %>% 
  rename(area_name = area_name.y)

Das Gleiche machen wir mit area_nr:

d_str5 <-
  d_str4 %>% 
  mutate(area_nr = ifelse(is.na(Gebietsnummer),
                                area_nr,
                                Gebietsnummer))
d_str5 %>% 
  slice_head(n=20) 
## # A tibble: 20 × 9
##    state    area_nr for_prop pop_density pop_move income unemp area_name        
##    <chr>    <chr>      <dbl>       <dbl>    <dbl>  <dbl> <dbl> <chr>            
##  1 Schlesw… 001          8.4       137.       9.5  21358   7   Flensburg – Schl…
##  2 Schlesw… 002          7.1        84.6      8.3  24354   6.5 Nordfriesland – …
##  3 Schlesw… 003          6.5       110.       4.6  22292   6.4 Steinburg – Dith…
##  4 Schlesw… 004          5.5       116.       8.6  23410   4.8 Rendsburg-Eckern…
##  5 Schlesw… 005         11.4      1879.      -1.8  19718   8.4 Kiel             
##  6 Schlesw… 006          8.7       171.       5.1  22081   6.7 Plön – Neumünster
##  7 Schlesw… 007         11.1       476.       8.1  24708   5.9 Pinneberg        
##  8 Schlesw… 008          8         240.       5.7  23952   5   Segeberg – Storm…
##  9 Schlesw… 009          5.7       144.       6.8  23411   6.1 Ostholstein – St…
## 10 Schlesw… 010          8.6       237.       8.8  24571   5.1 Herzogtum Lauenb…
## 11 Schlesw… 011         10         580        1.3  20404   8.5 Lübeck           
## 12 Schlesw… 01           8.4       184.       6    22833   6.3 Schleswig-Holste…
## 13 Mecklen… 012          6.7        73.9      2.6  19927   8   Schwerin – Ludwi…
## 14 Mecklen… 013          4          63.3      7.5  20014   6.8 Ludwigslust-Parc…
## 15 Mecklen… 014          5.5       278        6    19274   7.9 Rostock – Landkr…
## 16 Mecklen… 015          4.7        84.7      6.7  19231  11   Vorpommern-Rügen…
## 17 Mecklen… 016          3.8        53.8      2.8  18774   9.9 Mecklenburgische…
## 18 Mecklen… 017          3          40        4.2  19679   8.5 Mecklenburgische…
## 19 Mecklen… 13           4.7        69        5    19470   8.7 Mecklenburg-Vorp…
## 20 Hamburg  018         21.8      3044.       2.7  25029   8.1 Hamburg-Mitte    
## # … with 1 more variable: Gebietsnummer <chr>

Scheint zu passen.

d <-
  d_str5 %>% 
  left_join(elec_results2, by = c("area_nr" = "Gebietsnummer"))
dim(d)
## [1] 316  18

316 ist eine gute Zahl:

  • 299 Wahlbezirke +
  • 16 Länder +
  • 1 Bund

ergibt 316.

5.3 Check

Prüfen wir, ob es noch fehlende Werte nach dem Join gibt:

d %>% 
  filter(is.na(Gruppenname), is.na(Prozent)) %>% 
  select(area_name, area_nr, Prozent) %>% 
  nrow()
## [1] 0

Zählen wir die Anzahl der Wahleinheiten nach Art:

d %>% 
  group_by(Gebietsart) %>% 
  count()
## # A tibble: 3 × 2
## # Groups:   Gebietsart [3]
##   Gebietsart     n
##   <chr>      <int>
## 1 Bund           1
## 2 Land          16
## 3 Wahlkreis    299

Das sieht gut aus.

5.4 Geo-Daten

Die Geodaten sind ebenfalls erhältlich beim Bundeswahlleiter.

Die Geodaten zur Visualisierung der Wahlkreise werden im sog. “[Shape-Format](https://de.wikipedia.org/wiki/Shapefile” (.shp) geliefert.

Ich habe diese Version (der Shapefile, also der Geo-Daten) verwendet: “Geometrie der Wahlkreise im Koordinatensystem [UTM32](https://de.wikipedia.org/wiki/UTM-Koordinatensystem”, generalisierte Shape-Datei.

geo_file <- "data/btw21_geometrie_wahlkreise_shp/Geometrie_Wahlkreise_20DBT.shp"

Achtung: Wenn Sie die Daten selber herunterladen, passen Sie Ihren Pfad entsprechend in der Syntax oben an.

Bei mir gibt es einen Unterordner “data” in dem wiederum ein Unterordner “btw21_geometrie_wahlkreise_shp” existiert. Darin liegt eine Shape-Datei, auf die ich zugreife.

Einlesen:

wahlkreise_shp <- st_read(geo_file)
## Reading layer `Geometrie_Wahlkreise_20DBT' from data source 
##   `/Users/sebastiansaueruser/github-repos/afd-btw21-analyse/data/btw21_geometrie_wahlkreise_shp/Geometrie_Wahlkreise_20DBT.shp' 
##   using driver `ESRI Shapefile'
## Simple feature collection with 299 features and 4 fields
## Geometry type: MULTIPOLYGON
## Dimension:     XY
## Bounding box:  xmin: 280371.1 ymin: 5235856 xmax: 921120.1 ymax: 6101444
## Projected CRS: ETRS89 / UTM zone 32N

Hilfe zu st_read() findet sich hier oder auf der Dokumentation zum R-Paket sf (simple feature).

Plotten:

wahlkreise_shp %>%
  ggplot() +
  geom_sf()

Hilfe zum Geom sf (simple feature) findet sich hier.

Man beachte die Installationshinweise.

Der Aufbau des Datensatzes ist aufgeräumt:

glimpse(wahlkreise_shp)
## Rows: 299
## Columns: 5
## $ WKR_NR    <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 1…
## $ WKR_NAME  <chr> "Flensburg – Schleswig", "Nordfriesland – Dithmarschen Nord"…
## $ LAND_NR   <chr> "01", "01", "01", "01", "01", "01", "01", "01", "01", "01", …
## $ LAND_NAME <chr> "Schleswig-Holstein", "Schleswig-Holstein", "Schleswig-Holst…
## $ geometry  <MULTIPOLYGON [m]> MULTIPOLYGON (((545529.8 60..., MULTIPOLYGON ((…

5.5 Geo-Daten joinen

d2 <-
  d %>% 
  mutate(area_nr = as.integer(area_nr)) %>% 
  full_join(wahlkreise_shp, by = c("area_nr" = "WKR_NR"))

Jetzt haben wir drei Datensätze “gejoint” (“verheiratet”), also entsprechend passender Zeilen zusammengefügt.

5.6 Verheiratete Daten in einer Datei sichern

Als externe Datei sichern:

write_rds(d2, file = "objects/d2.rds")

Wir speichern nicht als Tabelle, da die Geo-Daten ein komplexeres Format haben. Stattdessen speichern als “normal” (binäre) Datei im R eigenen RDS-Format. Das ist ein effizientes, platzsparendes Format.

Sie können diese Daten dann so wieder in R importieren:

d2 <- read_rds(file = "objects/d2.rds")

Hier finden Sie die Datei in unserem Repo. Hier ist der Link zum direkten Download.

Sie können auch das ganze Repo herunterladen, wie oben erwähnt. Oder geschickter, Sie nutzen Github Desktop, dann können Sie jeweils die Updates herunterladen (und ggf. hochladen).

Dieses Vorgehen ist hilfreich, falls Sie oben Probleme hatten, alle Daten zu joinen. In dem Fall importieren Sie (mit read_rds()) einfach den Datensatz d2.

6 Geo-Vis

6.1 AfD-Anteil

d2 %>% 
  ggplot() +
  geom_sf(aes(fill = Prozent,
              geometry = geometry)) 

Andere Farbpalette:

d2 %>% 
  ggplot() +
  geom_sf(aes(fill = Prozent,
              geometry = geometry)) +
  scale_fill_distiller(palette = "Spectral")

Verschönern:

d2 %>% 
  ggplot() +
  geom_sf(aes(fill = Prozent,
              geometry = geometry),
          color = NA) +
  scale_fill_viridis_c() +
  theme_void() +
  labs(title = "Anteil der Zweitstimmen für die AfD",
       subtitle = "Bundestagswahl 2021",
        fill = "Anteil AfD-Zweitstimmen") +
  theme(legend.position = "bottom")

Oder probieren Sie mal bei der Farbpalette die Option option = "inferno".

Ein paar Wahlkreise haben entweder nicht erwischt, oder es gibt keine Daten.

6.2 Ausländeranteil

d2 %>% 
  ggplot() +
  geom_sf(aes(fill = for_prop,
              geometry = geometry),
          color = NA) +
  scale_fill_viridis_c() +
  theme_void()

6.3 Arbeitslosigkeit

d2 %>% 
  ggplot() +
  geom_sf(aes(fill = unemp,
              geometry = geometry),
          color = NA) +
  scale_fill_viridis_c() +
  theme_void()

6.4 Bevölkerungsdichte

d2 %>% 
  ggplot() +
  geom_sf(aes(fill = pop_density,
              geometry = geometry),
          color = NA) +
  scale_fill_viridis_c() +
  theme_void()

Oder vielleicht lieber Bevölkerungsdichte in der Log2-Skala?

d2 %>% 
  ggplot() +
  geom_sf(aes(fill = log2(pop_density),
              geometry = geometry),
          color = NA) +
  scale_fill_viridis_c() +
  theme_void()

So kommen die Unterschiede optisch deutlich besser zum Tragen.

Zur Erinnerung: +1 auf der Log2-Skala entspricht einer Multiplikation mit 2 auf der Rohskala. Die Log2-Skala zählt also “Verdopplungsschritte”.

6.5 Weiterführende Literatur

Bei Sauer 2019 findet sich auch einiges zum Thema Geoplotting.

7 EDA

7.1 Prädiktoren des AfD-Wahlerfolgs

7.1.1 Ausländeranteil

d2 %>% 
  filter(Gebietsart == "Wahlkreis") %>% 
  select(Prozent, for_prop) %>% 
  ggplot() +
  aes(x = for_prop, y = Prozent) +
  geom_point(alpha = .7) +
  geom_smooth()

Interessant! Ein einfacher linearer Trend liegt nicht vor. Vielleicht sehen wir eher zwei, unterschiedliche Cluster?

Cluster 1 ist geprägt von hohem Ausländeranteil und Cluster 2 von geringem. In beiden Clustern ist der Zusammenhang negativ. Allerdings ist dieser Zusammenhang deutlich stärker ausgeprägt für Cluster 1.

7.1.2 Zwei Cluster identifizieren

d2a <- 
d2 %>% 
  filter(Gebietsart == "Wahlkreis") %>% 
  mutate(Thueringen_Sachsen = 
           case_when(
             state == "Thüringen" |
               state == "Sachsen" ~ 
               TRUE,
             TRUE ~ FALSE
           ))

d2a %>%   
select(Prozent, for_prop, Thueringen_Sachsen) %>% 
  ggplot() +
  aes(x = for_prop, 
      y = Prozent, 
      color = Thueringen_Sachsen) +
  geom_point(alpha = .7) 

7.1.3 Ausländeranteil nach Bundesland

Wenn wir diese Analyse aufteilen nach Bundesländern, sehen wir vielleicht klarer.

d2 %>% 
  filter(Gebietsart == "Wahlkreis") %>% 
  select(Prozent, for_prop, state) %>% 
  ggplot() +
  aes(x = for_prop, y = Prozent, color = state) +
  geom_point(alpha = .7) +
  geom_smooth(method = "lm") +
  facet_wrap(~ state) +
  scale_y_continuous(limits = c(0, 40)) +
  theme(legend.position = "none")

Es scheint sich in jedem Bundesland ein negativer, linearer Zusammenhang zu zeigen. Es sei denn, der AfD-Anteil ist sehr gering, dann wird der Zusammenhang schwach, also eine Bodeneffekt.

7.2 Bivariate Korrelationen

d2 %>% 
  select(Prozent, for_prop, unemp, income, pop_density) %>% 
  cor_mat()
## # A tibble: 5 × 6
##   rowname     Prozent for_prop  unemp income pop_density
## * <chr>         <dbl>    <dbl>  <dbl>  <dbl>       <dbl>
## 1 Prozent       1        -0.58  0.038 -0.44       -0.3  
## 2 for_prop     -0.58      1     0.23   0.38        0.65 
## 3 unemp         0.038     0.23  1     -0.56        0.51 
## 4 income       -0.44      0.38 -0.56   1           0.046
## 5 pop_density  -0.3       0.65  0.51   0.046       1
d2 %>% 
  select(Prozent, for_prop, unemp, income, pop_density) %>% 
  cor_mat() %>% 
  cor_plot()

Das Kreuz zeigt wohl eine Korrelation nahe Null an.

Die Korrelationsmatrix im langen Format:

d2 %>% 
  select(Prozent, for_prop, unemp, income, pop_density) %>% 
  cor_mat() %>% 
  cor_gather() %>% 
  filter(cor != 1) %>% 
  filter(var1 == "Prozent") %>% 
  arrange(cor) %>% 
  gt() %>% 
  fmt_number(where(is.numeric), decimals = 2)
var1 var2 cor p
Prozent for_prop −0.58 0.00
Prozent income −0.44 0.00
Prozent pop_density −0.30 0.00
Prozent unemp 0.04 0.50

Je mehr Ausländer, oder auch je mehr Einkommen, desto weniger wird AfD gewählt. Die Arbeitslosigkeit steht fast nicht in einem (linearen) Zusammenhang mit dem AfD-Wahlerfolg.

8 Modellierung

8.1 Daten aufbereiten

Alle Prozessorkerne nutzen:

options(mc.cores = parallel::detectCores())

Die kompilierten Modelle auf die Hard Disk schreiben, das geht schneller, als jedes Mal neu zu berechnen:

rstan::rstan_options(auto_write = TRUE)

Alternativ könnte man das Ergebnisobjekt, wie m1 mit save(m1, file = "m1.rda") in eine Datei speichern.

Daten aufbereiten, d.h. z-standardisieren:

d3 <-
  d2 %>% 
  mutate(Prozent_z = scale(Prozent),
         for_prop_z = scale(for_prop),
         unemp_z = scale(unemp)) %>% 
  select(Prozent_z, state, for_prop_z, unemp_z, Gebietsname) %>% 
  filter(state != "Deutschland")

Wir nutzen z-Werte als AV; das hat auch pragmatische Gründe: z-Werte sind kontinuierlich und unbegrenzt und daher angenehm mit linearen Modellen zu untersuchen.

8.2 Modellkaskade

In der Forschungsfrage 2 untersuchen wir sozioökonomische Faktoren. Diese werden in den ersten Modellen analysiert.

Die Frage nach dem uniquen Erklärungsbeitrag der Bundesländer wird in Forschungsfrage 3 gestellt. Entsprechend werden Modelle aufgestellt, die den spezifischen Erklärungsbeitrag der Bundesländer (hinsichtlich der AV) untersuchen.

8.3 Ergebnisobjekt

In diesem Tibble (Dataframe, Tabelle) speichern wir zentrale Ergebnisse des Modells:

results <-
  tibble(
    model_id = NA,
    priors = "standard",
    preds = NA,
    r2 = NA,
    r2_loo = NA,
    coefs = NA
  )

8.4 Model 1: Arbeitslosigkeit

In diesem Modell wird der AfD-Wahlerfolg auf die Arbeitslosigkeit zurückgeführt.

8.4.1 Modell berechnen

tic()
m1 <- stan_glm(Prozent_z ~ unemp_z,
               data = d3,
               refresh = 0)
toc()
## 2.257 sec elapsed

8.4.2 Ergebnis

m1
## stan_glm
##  family:       gaussian [identity]
##  formula:      Prozent_z ~ unemp_z
##  observations: 315
##  predictors:   2
## ------
##             Median MAD_SD
## (Intercept) 0.0    0.1   
## unemp_z     0.0    0.1   
## 
## Auxiliary parameter(s):
##       Median MAD_SD
## sigma 1.0    0.0   
## 
## ------
## * For help interpreting the printed output see ?print.stanreg
## * For info on the priors used see ?prior_summary.stanreg

Oder kürzer:

coef(m1)
## (Intercept)     unemp_z 
## 0.001492462 0.037584874

Wie man sieht, spielt die Arbeitslosigkeit keine Rolle zur Erklärung des AfD-Wahlerfolgs.

plot(m1)

Man kann sich auch andere Darstellungen der Posteriori-Verteilung zeigen lassen:

mcmc_areas_ridges(m1,
                  pars = "unemp_z")

Offensichtlich kann es nicht ausgeschlossen werden, dass die Arbeitslosigkeit unabhängig vom AfD-Wahlerfolg ist.

8.4.2.1 R2

m1_r2 <- bayes_R2(m1)

Erklärung zu dieser Funktion findet sich hier.

8.4.3 Modellprüfung

8.4.3.1 PPV

pp_check(m1)

Oh, unser Modell erklärt die Daten schlecht.

tibble(
  resid = resid(m1),
  pred = predict(m1)
) %>% 
  ggplot() +
  aes(x = pred, y = resid) +
  geom_hline(yintercept = 0, linetype = "dotted") +
  geom_point() +
  geom_smooth()

Die Varianz ist nicht homogen über die Prädiktorwerte hinweg. Außerdem finden sich Anzeichen eines nichtlinearen Trends.

8.4.3.2 Kreuzvalidierung

LOO: “Leave-one-out Cross-validation”.

Mehr dazu hier oder hier.

m1_loo_r2 <- loo_R2(m1)
median(m1_loo_r2)
## [1] -0.004899671

8.4.4 Prioris

prior_summary(m1)
## Priors for model 'm1' 
## ------
## Intercept (after predictors centered)
##   Specified prior:
##     ~ normal(location = 0.00014, scale = 2.5)
##   Adjusted prior:
##     ~ normal(location = 0.00014, scale = 2.5)
## 
## Coefficients
##   Specified prior:
##     ~ normal(location = 0, scale = 2.5)
##   Adjusted prior:
##     ~ normal(location = 0, scale = 2.5)
## 
## Auxiliary (sigma)
##   Specified prior:
##     ~ exponential(rate = 1)
##   Adjusted prior:
##     ~ exponential(rate = 1)
## ------
## See help('prior_summary.stanreg') for more details
tic()
m1_prior_pv <- stan_glm(Prozent_z ~ unemp_z,
                        data = d3,
                        prior_PD = TRUE,
                        refresh = 0)
toc()
## 1.38 sec elapsed

Hinweise zu prior_PD finden sich hier.

coef(m1_prior_pv)
## (Intercept)     unemp_z 
##  0.00509050  0.08661521
plot(m1_prior_pv)

Die Prioris sehen vernünftig aus.

Vergleich der Priori-Verteilung mit der Post-Verteilung:

posterior_vs_prior(m1)

8.4.5 Fazit

m1_results <-
  tibble(
    model_id = "m1",
    priors = "standard",
    preds = "unemp_z",
    r2 = median(m1_r2),
    r2_loo = median(m1_loo_r2),
    coefs = list(coef(m1))
  )
results <-
  results %>% 
  bind_rows(m1_results)

8.5 Modell 2: Ausländeranteil

8.5.1 Modell berechnen (m2)

tic()
m2 <- 
  stan_glm(Prozent_z ~ for_prop_z,
           data = d3,
           refresh = 0
           )
toc()
## 1.187 sec elapsed

8.5.2 Ergebnis

m2
## stan_glm
##  family:       gaussian [identity]
##  formula:      Prozent_z ~ for_prop_z
##  observations: 315
##  predictors:   2
## ------
##             Median MAD_SD
## (Intercept)  0.0    0.0  
## for_prop_z  -0.6    0.0  
## 
## Auxiliary parameter(s):
##       Median MAD_SD
## sigma 0.8    0.0   
## 
## ------
## * For help interpreting the printed output see ?print.stanreg
## * For info on the priors used see ?prior_summary.stanreg

Modellparameter visualisieren:

plot(m2)

mcmc_areas(m2,
           pars = "for_prop_z")

Der Ausländeranteil spielt laut unserem Modell eine große Rolle.

8.5.2.1 R2

m2_r2 <- bayes_R2(m2)
median(m2_r2)
## [1] 0.333957

8.5.3 Modellprüfung

8.5.3.1 PPV

pp_check(m2)

Unser Modell erklärt die Daten nicht gut.

8.5.3.2 Kreuzvalidierung

m2_loo_r2 <- loo_R2(m2)
median(m2_loo_r2)
## [1] 0.3259591

8.5.4 Prioris

tic()
m2_prior_pd <- 
  stan_glm(Prozent_z ~ for_prop_z,
           data = d3,
           prior_PD = TRUE,
           refresh = 0
  )
toc()
## 1.193 sec elapsed
m2_prior_pd
## stan_glm
##  family:       gaussian [identity]
##  formula:      Prozent_z ~ for_prop_z
##  observations: 315
##  predictors:   2
## ------
##             Median MAD_SD
## (Intercept) 0.1    2.5   
## for_prop_z  0.0    2.5   
## 
## Auxiliary parameter(s):
##       Median MAD_SD
## sigma 0.7    0.7   
## 
## ------
## * For help interpreting the printed output see ?print.stanreg
## * For info on the priors used see ?prior_summary.stanreg
plot(m2_prior_pd,
      pars = "for_prop_z")

8.5.5 Fazit

m2_results <-
  tibble(
    model_id = "m2",
    priors = "standard",
    preds = "for_prop_z",
    r2 = median(m2_r2),
    r2_loo = median(m2_loo_r2),
    coefs = list(coef(m2))
  )
results <-
  results %>% 
  bind_rows(m2_results)

8.6 Modell 3: Arbeitslosigkeit und Ausländeranteil

8.6.1 Modell berechnen (m3)

tic()
m3 <- 
  stan_glm(Prozent_z ~ for_prop_z + unemp_z,
           data = d3,
           refresh = 0
           )
toc()
## 1.225 sec elapsed

8.6.2 Ergebnis

m3
## stan_glm
##  family:       gaussian [identity]
##  formula:      Prozent_z ~ for_prop_z + unemp_z
##  observations: 315
##  predictors:   3
## ------
##             Median MAD_SD
## (Intercept)  0.0    0.0  
## for_prop_z  -0.6    0.0  
## unemp_z      0.2    0.0  
## 
## Auxiliary parameter(s):
##       Median MAD_SD
## sigma 0.8    0.0   
## 
## ------
## * For help interpreting the printed output see ?print.stanreg
## * For info on the priors used see ?prior_summary.stanreg

Interessant. Zusätzlich zum Ausländeranteil spielt die Arbeitslosigkeit jetzt doch eine Rolle: Es findet sich jetzt eine positive Assoziation mit der AV.

plot(m3, plotfun = "mcmc_dens")

Details zu plot() im Zusammenhang von rstanarm finden sich hier.

8.6.2.1 R2

m3_r2 <- bayes_R2(m3)
median(m3_r2)
## [1] 0.3647048
m3_loo_r2 <- loo_R2(m3)
median(m3_loo_r2)
## [1] 0.3554038

8.6.3 Modellprüfung

pp_check(m3)

Hm, auch dieses Modell erklärt die Daten nicht gut.

8.6.4 Fazit

m3_results <-
  tibble(
    model_id = "m3",
    priors = "standard",
    preds = "unemp_z + for_prop_z",
    r2 = median(m2_r2),
    r2_loo = median(m2_loo_r2),
    coefs = list(coef(m2))
  )
results <-
  results %>% 
  bind_rows(m3_results)

8.7 Modell 4: Bundesländer

Frühere Analysen haben gezeigt, dass die Bundesländer über sozioökonomische Faktoren hinaus eine hohe prädiktive Relevanz in den Modellen haben.

8.7.1 Modell berechnen (m4)

tic()
m4 <- 
  stan_glm(Prozent_z ~ state,
           data = d3,
           refresh = 0
           )
toc()
## 1.927 sec elapsed

8.7.2 Ergebnis

m4
## stan_glm
##  family:       gaussian [identity]
##  formula:      Prozent_z ~ state
##  observations: 315
##  predictors:   16
## ------
##                             Median MAD_SD
## (Intercept)                 -0.2    0.1  
## stateBayern                 -0.1    0.1  
## stateBerlin                 -0.2    0.2  
## stateBrandenburg             1.5    0.2  
## stateBremen                 -0.4    0.3  
## stateHamburg                -0.8    0.2  
## stateHessen                 -0.1    0.1  
## stateMecklenburg-Vorpommern  1.4    0.2  
## stateNiedersachsen          -0.4    0.1  
## stateNordrhein-Westfalen    -0.4    0.1  
## stateRheinland-Pfalz        -0.1    0.1  
## stateSaarland                0.1    0.2  
## stateSachsen                 2.6    0.1  
## stateSachsen-Anhalt          1.7    0.2  
## stateSchleswig-Holstein     -0.5    0.2  
## stateThüringen               2.4    0.2  
## 
## Auxiliary parameter(s):
##       Median MAD_SD
## sigma 0.5    0.0   
## 
## ------
## * For help interpreting the printed output see ?print.stanreg
## * For info on the priors used see ?prior_summary.stanreg
plot(m4, regex_pars = "^state")

Sachsen und Thüringen haben die stärksten (absoluten und positiven) Koeffizienten.

m4_r2 <- bayes_R2(m4)
m4_r2_loo <- loo_R2(m4)

8.8 Modell 5: Sachsen und Thüringen vs. Rest

d4 <-
  d3 %>% 
  mutate(Sachsen_Thueringen = case_when(
    state == "Sachsen" | state == "Thüringen" ~ 1,
    TRUE ~ 0
  )) %>% 
  select(-state)

8.8.1 Modell berechnen (m5)

tic()
m5 <- 
  stan_glm(Prozent_z ~ Sachsen_Thueringen,
           data = d4,
           refresh = 0
           )
toc()
## 1.367 sec elapsed

8.8.2 Ergebnisse

m5
## stan_glm
##  family:       gaussian [identity]
##  formula:      Prozent_z ~ Sachsen_Thueringen
##  observations: 315
##  predictors:   2
## ------
##                    Median MAD_SD
## (Intercept)        -0.2    0.0  
## Sachsen_Thueringen  2.6    0.1  
## 
## Auxiliary parameter(s):
##       Median MAD_SD
## sigma 0.7    0.0   
## 
## ------
## * For help interpreting the printed output see ?print.stanreg
## * For info on the priors used see ?prior_summary.stanreg
plot(m5, pars = "Sachsen_Thueringen")

Das ist ein sehr starker Effekt.

m5_r2 <- bayes_R2(m5)
m5_r2_loo <- loo_R2(m5)

8.9 Modell 6: Alle Prädiktoren

8.9.1 Modell berechnen (m6)

tic()
m6 <- 
  stan_glm(Prozent_z ~ state + for_prop_z + unemp_z,
           data = d3,
           refresh = 0
           )
toc()
## 1.697 sec elapsed

8.9.2 Ergebnisse

m6
## stan_glm
##  family:       gaussian [identity]
##  formula:      Prozent_z ~ state + for_prop_z + unemp_z
##  observations: 315
##  predictors:   18
## ------
##                             Median MAD_SD
## (Intercept)                  0.3    0.1  
## stateBayern                 -0.2    0.1  
## stateBerlin                 -0.6    0.2  
## stateBrandenburg             0.7    0.2  
## stateBremen                 -1.0    0.3  
## stateHamburg                -1.1    0.2  
## stateHessen                 -0.2    0.1  
## stateMecklenburg-Vorpommern  0.3    0.2  
## stateNiedersachsen          -0.9    0.1  
## stateNordrhein-Westfalen    -0.9    0.1  
## stateRheinland-Pfalz        -0.4    0.1  
## stateSaarland               -0.5    0.2  
## stateSachsen                 1.7    0.2  
## stateSachsen-Anhalt          0.7    0.2  
## stateSchleswig-Holstein     -1.1    0.2  
## stateThüringen               1.6    0.2  
## for_prop_z                  -0.3    0.0  
## unemp_z                      0.2    0.0  
## 
## Auxiliary parameter(s):
##       Median MAD_SD
## sigma 0.4    0.0   
## 
## ------
## * For help interpreting the printed output see ?print.stanreg
## * For info on the priors used see ?prior_summary.stanreg
m6_r2 <- bayes_R2(m6)
m6_r2_loo <- loo_R2(m6)
median(m6_r2_loo)
## [1] 0.8211281

8.9.3 Modellprüfung

pp_check(m6)

Das Modell erklärt die Daten gut.

8.10 Modell 7: Ausländer, Arbeitslosigkeit, Sachsen-Thüringen

8.10.1 Modell berechnen (m7)

tic()
m7 <- 
  stan_glm(Prozent_z ~ Sachsen_Thueringen + for_prop_z + unemp_z,
           data = d4,
           refresh = 0
           )
toc()
## 1.257 sec elapsed

8.10.2 Ergebnisse

m7
## stan_glm
##  family:       gaussian [identity]
##  formula:      Prozent_z ~ Sachsen_Thueringen + for_prop_z + unemp_z
##  observations: 315
##  predictors:   4
## ------
##                    Median MAD_SD
## (Intercept)        -0.2    0.0  
## Sachsen_Thueringen  2.1    0.1  
## for_prop_z         -0.4    0.0  
## unemp_z             0.1    0.0  
## 
## Auxiliary parameter(s):
##       Median MAD_SD
## sigma 0.6    0.0   
## 
## ------
## * For help interpreting the printed output see ?print.stanreg
## * For info on the priors used see ?prior_summary.stanreg
m7_r2 <- bayes_R2(m7)
m7_r2_loo <- loo_R2(m7)
median(m7_r2_loo)
## [1] 0.6278403

8.11 Modell 8: Nur Sachsen-Thüringen

8.11.1 Modell berechnen (m8)

tic()
m8 <- 
  stan_glm(Prozent_z ~ Sachsen_Thueringen,
           data = d4,
           refresh = 0
           )
toc()
## 1.343 sec elapsed

8.11.2 Ergebnisse

m8
## stan_glm
##  family:       gaussian [identity]
##  formula:      Prozent_z ~ Sachsen_Thueringen
##  observations: 315
##  predictors:   2
## ------
##                    Median MAD_SD
## (Intercept)        -0.2    0.0  
## Sachsen_Thueringen  2.6    0.1  
## 
## Auxiliary parameter(s):
##       Median MAD_SD
## sigma 0.7    0.0   
## 
## ------
## * For help interpreting the printed output see ?print.stanreg
## * For info on the priors used see ?prior_summary.stanreg
m8_r2 <- bayes_R2(m8)
m8_r2_loo <- loo_R2(m8)
median(m8_r2_loo)
## [1] 0.5020004

8.12 Modell 9: Sachsen-Thüringen und Ausländer

8.12.1 Modell berechnen (m8)

tic()
m9 <- 
  stan_glm(Prozent_z ~ Sachsen_Thueringen + for_prop_z,
           data = d4,
           refresh = 0
  )
toc()
## 2.51 sec elapsed

8.12.2 Ergebnisse

m9
## stan_glm
##  family:       gaussian [identity]
##  formula:      Prozent_z ~ Sachsen_Thueringen + for_prop_z
##  observations: 315
##  predictors:   3
## ------
##                    Median MAD_SD
## (Intercept)        -0.2    0.0  
## Sachsen_Thueringen  2.1    0.1  
## for_prop_z         -0.4    0.0  
## 
## Auxiliary parameter(s):
##       Median MAD_SD
## sigma 0.6    0.0   
## 
## ------
## * For help interpreting the printed output see ?print.stanreg
## * For info on the priors used see ?prior_summary.stanreg
m9_r2 <- bayes_R2(m9)
m9_r2_loo <- loo_R2(m9)
median(m9_r2_loo)
## [1] 0.6173961

8.12.3 Visualisierung

Es lohnt sich vielleicht, dieses Modell zu visualisieren.

m9_draws <-
  m9 %>% 
  as_tibble() %>% 
  rename(intercept = `(Intercept)`) %>% 
  dplyr::select(-sigma)
ggplot(d4) + 
  aes(x = for_prop_z, y = Prozent_z) +
  # Ungewissheit der Regressionsgeraden:
  geom_abline(
    aes(intercept = intercept, slope = for_prop_z),
    data = sample_n(m9_draws, 100),
    color = "grey60",
    alpha = .15
  ) +
  # Mediane Schätzwerte für "die" Regressionsgerade:
  geom_abline(
    intercept = median(m9_draws$intercept),
    slope = median(m9_draws$for_prop_z),
    size = 1,
    color = "blue"
  ) +
  geom_point()

8.13 Modelle vergleichen

loo_m1 <- loo(m1)
loo_m2 <- loo(m2)
loo_m3 <- loo(m3)
loo_m4 <- loo(m4)
loo_m5 <- loo(m5)
loo_m6 <- loo(m6, k_threshold = 0.7)
loo_m7 <- loo(m7)
loo_m8 <- loo(m8)
loo_m9 <- loo(m9)
loo_comparison <- 
  loo_compare(
    loo_m1,
    loo_m2,
    loo_m3,
    loo_m4,
    loo_m5,
    loo_m6,
    loo_m7,
    loo_m8,
    loo_m9
  )
loo_comparison 
##    elpd_diff se_diff
## m6    0.0       0.0 
## m4  -36.7       7.8 
## m7 -113.6      18.1 
## m9 -118.6      19.4 
## m8 -160.6      22.4 
## m5 -160.6      22.4 
## m3 -201.4      23.7 
## m2 -208.3      23.6 
## m1 -272.2      25.4

Bestes Modell:

loo_min_model <-
attr(loo_comparison, "dimnames")[[1]][1]

loo_min_model
## [1] "m6"

Hilfe zum Vergleich des LOO-Werte findet sich hier.

Modell m6 tat sich als bestes Modell hervor.

9 Fazit

9.1 Fazit der Modellierung

Modell m7 kam als bestes Modell im Vergleich hervor.

9.2 Interpretation

Zwar spielen die sozioökonomischen Kennwerte eine Rolle, aber ein wichtiger Faktor zur Vorhersage des Wahlerfolg sind die Bundesländer. Dabei sticht das Duo Thüringen-Sachsen hervor. Diese beiden Bundesländer haben - über den Beitrag der sozioökonomischen Kennwerte hinaus - einen besonders großen Beitrag zur Erklärung der AfD-Wahlergebnisse.

Das Modell schickt uns damit gleichsam zurück an den Schreibtisch: Wir brauchen eine Theorie, die erklären könnte, warum gerade die Bundesländer und gerade bestimmte Bundesländer Informationen in sich bergen, die den AfD-Wahlerfolg besonders gut erklären können.

Die Frage nach dem “warum” ist eine kausale; wissenschaftliche (und sonstige!) Theorien sind zumeist an den kausalen Abhängigkeiten interessiert. Das hat auch praktische Gründe: Nur, wenn man das “warum” kennt, kann man Interventionen einleiten; sonst wüsste man nicht, welche Interventionen bei welchen Variablen welchen Nutzen haben.

9.3 Ausblick

In Mehrebenenmodell wäre noch ein sinnvolle Erweiterung.

Ein nützliches Tutorial findet sich hier.